﻿////////////////////////////////////////////////////////////////////////////////////////////////////
//
// VSCoverageToEmma - Visual Studio Coverage file to Emma converter
// Copyright (c) Kouji Matsui, All rights reserved.
//
// * Redistribution and use in source and binary forms, with or without modification,
//   are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Xsl;
using VSCoverageToEmma.Analyzer;
using VSCoverageToEmma.Analyzer.EmmaViews;

namespace VSCoverageToEmma
{
	public sealed class VSCoverageToEmmaConverter
	{
		private static readonly XmlWriterSettings writerSettings_ = new XmlWriterSettings()
			{
				ConformanceLevel = ConformanceLevel.Auto,
				Encoding = Encoding.UTF8,
				Indent = true,
				IndentChars = "\t",
				NewLineChars = Environment.NewLine,
				OmitXmlDeclaration = false
			};

		private readonly IVSCoverageConverter converter_;
		private readonly SynchronizationContext context_;

		public VSCoverageToEmmaConverter(IVSCoverageConverter converter)
			: this(converter, SynchronizationContext.Current)
		{
		}

		public VSCoverageToEmmaConverter(IVSCoverageConverter converter, SynchronizationContext context)
		{
			converter_ = converter;
			context_ = context;
		}

		public EventHandler<ConvertedEventArgs> Converted;

		private void OnConverted(string vsCoveragePath, string emmaPath, int methods)
		{
			var converted = this.Converted;
			if (converted != null)
			{
				if (context_ != null)
				{
					context_.Post(dummy => converted(this, new ConvertedEventArgs(vsCoveragePath, emmaPath, methods)), null);
				}
				else
				{
					converted(this, new ConvertedEventArgs(vsCoveragePath, emmaPath, methods));
				}
			}
		}

		private static string SanitizeFolderPath(string path)
		{
			return (path.EndsWith(@"\") == true) ? path : (path + @"\");
		}

		[Conditional("DEBUG")]
		private static void WriteTables(string emmaPath, DataSet dataSet)
		{
			foreach (DataTable table in dataSet.Tables)
			{
				using (var stream = new FileStream(
					string.Format("{0}.{1}.csv", emmaPath, table.TableName),
					FileMode.Create,
					FileAccess.ReadWrite,
					FileShare.None))
				{
					var tw = new StreamWriter(stream, Encoding.UTF8);

					tw.WriteLine(table.Columns.
						Cast<DataColumn>().
						Select(column => string.Format("\"{0}\"", column.ColumnName)).
						Join(","));

					foreach (DataRow row in table.Rows)
					{
						tw.WriteLine(row.ItemArray.
							Select(item =>
								{
									if (item is string)
									{
										return string.Format("\"{0}\"", item.ToString().Replace("\"", "\"\""));
									}
									else if (item != null)
									{
										return item.ToString();
									}
									else
									{
										return "(null)";
									}
								}).
							Join(","));
					}

					tw.Flush();
					stream.Close();
				}
			}
		}

		private static void WriteStat(XmlWriter xw, string statName, EmmaContains contains)
		{
			xw.WriteStartElement(statName);
			xw.WriteAttributeString("value", contains.TotalCount.ToString());
			xw.WriteEndElement();
		}

		public int ConvertCoverage(string vsCoveragePath, string emmaPath)
		{
			EmmaAnalyzer analyzer;
			using (var dataSet = converter_.Convert(vsCoveragePath))
			{
				WriteTables(emmaPath, dataSet);
				analyzer = new EmmaAnalyzer("all classes", dataSet);
			}

			using (var stream = new FileStream(
				emmaPath,
				FileMode.Create,
				FileAccess.ReadWrite,
				FileShare.None))
			{
				var xw = XmlWriter.Create(stream, writerSettings_);

				xw.WriteProcessingInstruction("xml", "version=\"1.0\"");

				xw.WriteStartElement("report");

				xw.WriteStartElement("stats");
				WriteStat(xw, "packages", analyzer.Packages);
				WriteStat(xw, "classes", analyzer.Classes);
				WriteStat(xw, "methods", analyzer.Methods);
				WriteStat(xw, "srcfiles", analyzer.SourceFiles);
				WriteStat(xw, "srclines", analyzer.Lines);
				xw.WriteEndElement();

				xw.WriteStartElement("data");
				analyzer.Write(xw, "all");
				xw.WriteEndElement();

				xw.WriteEndElement();

				xw.Flush();
				stream.Close();

				this.OnConverted(vsCoveragePath, emmaPath, analyzer.Methods.TotalCount);

				return analyzer.Methods.TotalCount;
			}
		}

		public int ConvertCoverages(string vsCoveragesFolderPath, IEnumerable<string> vsCoverageRelativePaths, string emmaFolderPath)
		{
			var vsCoverageFolderSanitizedPath = SanitizeFolderPath(vsCoveragesFolderPath);
			var emmaFolderSanitizedPath = SanitizeFolderPath(emmaFolderPath);

			var count = 0;

			Parallel.ForEach(
				from vsCoverageRelativePath in
					vsCoverageRelativePaths
				let vsCoveragePath = Path.Combine(vsCoveragesFolderPath, vsCoverageRelativePath)
				let fileName = Path.GetFileNameWithoutExtension(vsCoverageRelativePath)
				let emmaPath = Path.Combine(emmaFolderSanitizedPath, Path.Combine(Path.GetDirectoryName(vsCoverageRelativePath), fileName)) + ".xml"
				select new
				{
					VSCoveragePath = vsCoveragePath,
					EmmaPath = emmaPath
				},
				entry =>
				{
					var result = this.ConvertCoverage(entry.VSCoveragePath, entry.EmmaPath);
					Interlocked.Add(ref count, result);
				});

			return count;
		}

		public int ConvertCoverages(string vsCoveragesFolderPath, string emmaFolderPath)
		{
			var vsCoverageFolderSanitizedPath = SanitizeFolderPath(vsCoveragesFolderPath);
			var emmaFolderSanitizedPath = SanitizeFolderPath(emmaFolderPath);

			return this.ConvertCoverages(
				vsCoverageFolderSanitizedPath,
				from path in ConverterHelper.TraverseFiles(vsCoverageFolderSanitizedPath, "*.coverage")
				select path.Substring(vsCoverageFolderSanitizedPath.Length),
				emmaFolderSanitizedPath);
		}
	}
}
